package com.example.thirdjarlearningcore.postProcessor;

import com.example.thirdjarlearningcore.annotation.ThirdJarCotAsBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import static org.springframework.util.Assert.notNull;

/**
 * 该类实现 BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware 四个接口
 * BeanDefinitionRegistryPostProcessor 接口本身就可以执行动态注册。也可以调用其他实现了注册功能的方法来实现
 *
 * 一个非常重要的点是，我们是无法调用这个类的，这个类将会被SpringBoot视作BeanDefinitionRegistryPostProcessor自动调用，慢慢来理解就行。
 * 注意，这个类上没有使用任何的@Component/@Configuration注解
 */
public class ThirdJarCotAsBeanScanRegister implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

    // 这个类中的私有变量完全随便，要用到啥就定义啥。
    // 注意，这些私有变量完全没有get/set方法，这个类也没有构造函数。
    // 还是那个原因，我们是无法人为调用这个类的。事实上，这个类会被SpringBoot制作成Bean

    // 自定义的私有变量 basePackage，用于保存 扫描哪个路径下的类是否带有@ThirdJarCotAsBean注解
    private String basePackage;
    // 下面四个都是自定义的私有变量，但是在该类乃至整个项目中都没有使用，也没有get/set方法
    // 这四个自定义的变量完全就是为了说明，这个类中有啥私有变量完全是随意的，且写不写get/set方法也无所谓，因为根本用不上
    // 那么这些变量的初始化赋值是在什么时候呢？分两类:
    // 如果是下面的各种接口必实现方法的输入类，则就是在接口调用实现方法时可以直接将输入值赋给this私有变量
    // 如果不是，那么是在starter的ThirdJarLearningAutoConfiguration.AutoConfiguredThirdJarCotAsBeanScanRegistrar方法中，到那时再讲
    private String beanName;
    private BeanDefinitionRegistry beanDefinitionRegistry;
    private BeanFactory beanFactory;
    private ApplicationContext applicationContext;

    /**
     * 接口必实现类。保持空方法体也可
     */
    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    /**
     * 接口必实现类。保持空方法体也可
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        notNull(this.basePackage, "Property 'basePackage' is required");
    }

    /**
     * 注册核心
     * 这个方法看名字也能看出来，在默认的BeanDefinition加载完成后，执行这个方法继续注册Bean
     * 如果我们只是要注册一个明确已知的特定类Bean，完全可以在这一步中直接注册
     * 直接用命令 BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(目标类.class);
     * 然后用 builder.getBeanDefinition() 获取内部的BeanDefinition后进行其他的设置，或者直接对 builder 进行设置，也可以左右内部的 BeanDefinition
     * 最后使用 register.registerBeanDefinition(beanName, builder.getBeanDefinition()) 注册到容器中即可
     *
     * 但是由于我们现在完全未知，只能经过扫描后才能知道被 @ThirdJarCotAsBean 注解的都是那些类
     * 因此在这个方法中还并没有直接执行注册，而是调用一个scanner来执行注册
     *
     * @param registry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 使用当前的 registry 初始化一个自定义的 scanner
        ThirdJarCatAsBeanScanner thirdJarCatAsBeanScanner = new ThirdJarCatAsBeanScanner(registry);
        // 为这个 scanner 配置一些内容。这里是配置 scanner 对扫描到的类创建成BeanDefinition后，以什么名字注册为bean
        thirdJarCatAsBeanScanner.setBeanNameGenerator((beanDefinition, beanDefinitionRegistry) -> {
            // 两个输入变量是固定的。
            String beanClassName = beanDefinition.getBeanClassName();
            try {
                // 根据BeanDefinition获取到元类信息
                Class<?> tarClass = Class.forName(beanClassName);
                // 在元类上获取 @ThirdJarCotAsBean 注解
                ThirdJarCotAsBean tarClassAnnotation = tarClass.getAnnotation(ThirdJarCotAsBean.class);
                if (null == tarClassAnnotation) {
                    return null;
                }
                //如果@ThirdJarCotAsBean没有指定名字,那么默认以类名首字母小写进行注册
                if (tarClassAnnotation.beanName().equalsIgnoreCase("")) {
                    String tarClassSimpleName = tarClass.getSimpleName();
                    String first = String.valueOf(tarClassSimpleName.charAt(0));
                    return tarClassSimpleName.replaceFirst(first, first.toLowerCase());
                } else {
                    return tarClassAnnotation.beanName();
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                return null;
            }
        });
        // 然后执行 scanner的doScan方法，传入的 this.basePackage 将会限定 scanner 只在这个范围内进行扫描
        // 显然并没有配置 scanner 的详细扫描配置，转到 ThirdJarCatAsBeanScanner 源码
        thirdJarCatAsBeanScanner.doScan(this.basePackage);
    }

    /**
     * 接口必实现类。保持空方法体也可
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    }

    /**
     * 接口必实现类。保持空方法体也可。
     * 该接口之前也用过，就是当每次ApplicationContext发生改变时自动调用
     * 但是注意，这里说的ApplicationContext是starter的，而不是use的
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
